#include "structMeshIdxTranslation.hpp"
#include "structMeshFunctions.hpp"
#include <iostream>

namespace hms {

namespace edgeIdxTranslation {

	void assertValidSizes(
		[[maybe_unused]] const Ref<const Array2i>& meshSizeGlobal,
		[[maybe_unused]] const Ref<const Array2i>& meshSizeLocal,
		[[maybe_unused]] const Ref<const Array2i>& offset
	){
		assert( (meshSizeGlobal >= meshSizeLocal+offset).all() &&
"Edge index translation: \
Global mesh size must be larger or equal to local mesh size!"
		);
		assert( (offset < meshSizeGlobal).all() &&
"Edge index translation: \
Local offset must be smaller than global mesh size!"
		);
	}

	void assertOrientation(
		[[maybe_unused]] Index idx,
		[[maybe_unused]] const Ref<const Array2i>& meshSize,
		[[maybe_unused]] bool horizontal
	){
		assert( isHorizontal(idx, meshSize) == horizontal &&
"Index passed to translation function resolves to edge with wrong orientation!"
		);
	}
}

Index edgeLocalToGlobal(
	Index idx,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSizeLocal,
	const Ref<const Array2i>& offset
){
	if ( isHorizontal(idx, meshSizeLocal) )
		return hEdgeLocalToGlobal(idx,meshSizeGlobal,meshSizeLocal,offset);
	else
		return vEdgeLocalToGlobal(idx,meshSizeGlobal,meshSizeLocal,offset);
}

Index edgeGlobalToLocal(
	Index idx,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSizeLocal,
	const Ref<const Array2i>& offset
){
	if ( isHorizontal(idx, meshSizeGlobal) )
		return hEdgeGlobalToLocal(idx,meshSizeGlobal,meshSizeLocal,offset);
	else
		return vEdgeGlobalToLocal(idx,meshSizeGlobal,meshSizeLocal,offset);
}

Index hEdgeLocalToGlobal(
	Index idx,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSizeLocal,
	const Ref<const Array2i>& offset
){
	edgeIdxTranslation::assertValidSizes(meshSizeGlobal,meshSizeLocal,offset);
	edgeIdxTranslation::assertOrientation(idx, meshSizeLocal, true);
	return
		( idx / meshSizeLocal.x() + offset.y() ) * meshSizeGlobal.x()
		+ idx % meshSizeLocal.x() + offset.x();
}

Index vEdgeLocalToGlobal(
	Index idx,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSizeLocal,
	const Ref<const Array2i>& offset
){
	edgeIdxTranslation::assertValidSizes(meshSizeGlobal,meshSizeLocal,offset);
	edgeIdxTranslation::assertOrientation(idx, meshSizeLocal, false);
	Index nHEdgesGlobal { meshSizeGlobal.x() * (meshSizeGlobal.y() + 1)  };
	Index modIdx { idx - ( meshSizeLocal.x() * (meshSizeLocal.y() + 1) ) };
	return
		nHEdgesGlobal +
		( modIdx / (meshSizeLocal.x()+1) + offset.y() ) * (meshSizeGlobal.x()+1)
		+ modIdx % (meshSizeLocal.x()+1) + offset.x();
}

Index hEdgeGlobalToLocal(
	Index idx,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSizeLocal,
	const Ref<const Array2i>& offset
){
	edgeIdxTranslation::assertValidSizes(meshSizeGlobal,meshSizeLocal,offset);
	edgeIdxTranslation::assertOrientation(idx, meshSizeGlobal, true);
	assert(
		idx / meshSizeGlobal.x() - offset.y() <= meshSizeLocal.y() &&
		idx % meshSizeGlobal.x() - offset.x() <  meshSizeLocal.x() &&
		"Global edge index (horizontal) is outside of local mesh partition!" );
	
	return
		( idx / meshSizeGlobal.x() - offset.y() ) * meshSizeLocal.x()
		+ idx % meshSizeGlobal.x() - offset.x();
}

Index vEdgeGlobalToLocal(
	Index idx,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSizeLocal,
	const Ref<const Array2i>& offset
){
	edgeIdxTranslation::assertValidSizes(meshSizeGlobal,meshSizeLocal,offset);
	edgeIdxTranslation::assertOrientation(idx, meshSizeGlobal, false);

	Index nHEdgesLocal { meshSizeLocal.x() * (meshSizeLocal.y() + 1)  };
	Index modIdx { idx - ( meshSizeGlobal.x() * (meshSizeGlobal.y() + 1) ) };
	
	assert(
		modIdx / (meshSizeGlobal.x()+1) - offset.y() <  meshSizeLocal.y() &&
		modIdx % (meshSizeGlobal.x()+1) - offset.x() <= meshSizeLocal.x() &&
		"Global edge index (vertical) is outside of local mesh partition!" );
	
	return
		nHEdgesLocal +
		( modIdx / (meshSizeGlobal.x()+1) - offset.y() ) * (meshSizeLocal.x()+1)
		+ modIdx % (meshSizeGlobal.x()+1) - offset.x();
}


}